/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
/*
 * This copy of Woodstox XML processor is licensed under the
 * Apache (Software) License, version 2.0 ("the License").
 * See the License for details about distribution rights, and the
 * specific rights regarding derivate works.
 *
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing Woodstox, in file "ASL2.0", under the same directory
 * as this file.
 */
package com.je.bpm.engine.impl.cmd;

import com.google.common.base.Strings;
import com.je.bpm.core.model.BpmnModel;
import com.je.bpm.core.model.FlowElement;
import com.je.bpm.core.model.FlowNode;
import com.je.bpm.core.model.SequenceFlow;
import com.je.bpm.core.model.event.EndEvent;
import com.je.bpm.core.model.event.StartEvent;
import com.je.bpm.core.model.gateway.InclusiveGateway;
import com.je.bpm.core.model.task.*;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.ActivitiObjectNotFoundException;
import com.je.bpm.engine.RepositoryService;
import com.je.bpm.engine.TaskService;
import com.je.bpm.engine.delegate.DelegateHelper;
import com.je.bpm.engine.impl.interceptor.Command;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.TaskEntity;
import com.je.bpm.engine.task.Task;
import com.je.bpm.runtime.shared.identity.ResultUserParser;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 获取提交任务节点信息
 */
public class FindSubmitElementAssigneeCmd implements Command<Object>, Serializable {

    private static final long serialVersionUID = 1L;
    protected String target;
    protected String taskId;
    protected String pdid;
    protected Map<String, Object> bean;
    protected String prod;
    protected ResultUserParser resultUserParser;
    protected String adjust;
    protected String operationId;

    public FindSubmitElementAssigneeCmd(String pdid, String taskId, String target, Map<String, Object> bean, String prod, String adjust, String operationId) {
        this.taskId = taskId;
        this.pdid = pdid;
        this.target = target;
        this.bean = bean;
        this.prod = prod;
        this.adjust = adjust;
        this.operationId = operationId;
    }

    @Override
    public Object execute(CommandContext commandContext) {
        resultUserParser = commandContext.getProcessEngineConfiguration().getResultUserParser();
        if (Strings.isNullOrEmpty(taskId)) {
            return findSponsorNextElement(commandContext);
        }
        TaskEntity task = commandContext.getTaskEntityManager().findById(taskId);
        if (task == null) {
            throw new ActivitiObjectNotFoundException("Cannot find task with id " + taskId, Task.class);
        }
        if (task.isSuspended()) {
            throw new ActivitiException("this task is suspended");
        }
        return findNextElementAssignees(commandContext, task, operationId);
    }

    /**
     * 发起可以提交的节点信息
     *
     * @param commandContext
     * @return
     */
    private Object findSponsorNextElement(CommandContext commandContext) {
        Object result = new Object();
        RepositoryService repositoryService = commandContext.getProcessEngineConfiguration().getRepositoryService();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pdid);
        List<FlowElement> list = bpmnModel.getMainProcess().getFlowElementList();
        for (FlowElement flowElement : list) {
            if (flowElement instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) flowElement;
                FlowElement firstNode = startEvent.getOutgoingFlows().get(0);
                String targetRef = ((SequenceFlow) firstNode).getTargetRef();
                for (FlowElement kaiteUserTask : list) {
                    if (kaiteUserTask.getId().equals(targetRef)) {
                        //TODO 下个节点如果是分支聚合则跳过 看下个节点是否配置了可跳跃，并且可处理中是否包含自己 如果是接着寻找下下个节点直到不满足
                        FlowNode flowNode = getNextElement(commandContext, (KaiteTask) kaiteUserTask, true);
                        Boolean isJump = false;
                        if (!flowNode.getId().equals(kaiteUserTask.getId())) {
                            isJump = true;
                        }
                        List<SequenceFlow> outgoingList = flowNode.getOutgoingFlows();
                        result = buildGoingList(outgoingList, commandContext.getProcessEngineConfiguration().getTaskService(), isJump, flowNode, operationId);
                        break;
                    }
                }
                break;
            }
        }
        return result;
    }

    private Object findNextElementAssignees(CommandContext commandContext, TaskEntity task, String operationId) {
        List<Object> result = new ArrayList<>();
        RepositoryService repositoryService = commandContext.getProcessEngineConfiguration().getRepositoryService();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        FlowElement taskFlowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        List<SequenceFlow> outgoingList = new ArrayList<>();
        if (taskFlowElement == null) {
            throw new ActivitiException(String.format("Can't find task %s definiton from the process.", task.getName()));
        }
        KaiteTask kaiteTask = (KaiteTask) taskFlowElement;
        //TODO 看下个节点是否配置了可跳跃，并且可处理中是否包含自己 如果是接着寻找下下个节点直到不满足
        FlowElement element = getNextElement(commandContext, kaiteTask, false);
        Boolean isJump = false;
        if (!kaiteTask.getId().equals(element.getId())) {
            isJump = true;
        }
        if (element instanceof KaiteLoopUserTask) {
            KaiteTask kaiteTaskNow = (KaiteTask) element;
            result.add(kaiteTaskNow.getIncomingFlows().get(0));
        }
        FlowNode flowNode = (FlowNode) element;
        //如果是调拨查看人员，直接查询
        if (adjust.equals("1")) {
            outgoingList.add((SequenceFlow) bpmnModel.getFlowElement(target));
        } else {
            outgoingList = flowNode.getOutgoingFlows();
            if (outgoingList.isEmpty()) {
                throw new ActivitiException(String.format("Can't find next node", task.getName()));
            }
        }
        return buildGoingList(outgoingList, commandContext.getProcessEngineConfiguration().getTaskService(), isJump, flowNode, operationId);
    }

    private Object buildGoingList(List<SequenceFlow> outgoingList, TaskService taskService, Boolean isJump, FlowNode flowNode, String operationId) {
        List<Object> listUser = new ArrayList<>();
        //如果是调拨查看人员，直接查询
        if (adjust.equals("1")) {
            Object user = buildUserTask(outgoingList.get(0), taskService, false, operationId);
            if (user != null) {
                listUser.add(user);
            }
            return listUser.toArray();
        }
        for (SequenceFlow sequenceFlow : outgoingList) {
            if (!isJump || (isJump && flowNode instanceof KaiteDecideUserTask) || (isJump && flowNode instanceof KaiteUserTask)) {
                if (!sequenceFlow.getId().equals(target)) {
                    continue;
                }
            }
            if (sequenceFlow.getTargetFlowElement() instanceof InclusiveGateway) {
                List<SequenceFlow> list = ((InclusiveGateway) sequenceFlow.getTargetFlowElement()).getOutgoingFlows();
                for (SequenceFlow childrenSequenceFlow : list) {
                    if (DelegateHelper.isBoolean(childrenSequenceFlow.getConditionExpression(), bean)) {
                        Object user = buildUserTask(childrenSequenceFlow, taskService, isJump, operationId);
                        if (user != null) {
                            listUser.add(user);
                        }
                    }
                }
            } else {
                if (DelegateHelper.isBoolean(sequenceFlow.getConditionExpression(), bean)) {
                    Object user = buildUserTask(sequenceFlow, taskService, isJump, operationId);
                    if (user != null) {
                        listUser.add(user);
                    }
                }
            }
        }
        return listUser.toArray();
    }

    private Object buildUserTask(SequenceFlow sequenceFlow, TaskService taskService, Boolean isJump, String operationId) {
        if (sequenceFlow.getTargetFlowElement() instanceof KaiteBaseUserTask) {
            KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) sequenceFlow.getTargetFlowElement();
            Boolean multiple = false;
            if (kaiteBaseUserTask instanceof KaiteMultiUserTask ||
                    kaiteBaseUserTask instanceof KaiteCounterSignUserTask ||
                    kaiteBaseUserTask instanceof KaiteCandidateUserTask ||
                    kaiteBaseUserTask instanceof KaiteRandomUserTask) {
                multiple = true;
            }
            FlowNode flowNode = null;
            if (isJump) {
                flowNode = (FlowNode) sequenceFlow.getSourceFlowElement();
            }
            return buildUsersInfo(multiple, kaiteBaseUserTask, taskService, flowNode, operationId);
        }
        return null;
    }


    private Object buildUsersInfo(Boolean multiple, KaiteBaseUserTask kaiteBaseUserTask,
                                  TaskService taskService, FlowNode flowNode, String operationId) {
        return taskService.getAssignment(true, multiple, pdid, kaiteBaseUserTask, taskId, prod, bean, flowNode, operationId);
    }

    /**
     * @param commandContext
     * @param flowNode       当前节点
     */
    private FlowNode getNextElement(CommandContext commandContext, FlowNode flowNode, Boolean isSponsor) {
        RepositoryService repositoryService = commandContext.getProcessEngineConfiguration().getRepositoryService();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pdid);
        //如果目标节点是网关节点 找可以走下去的唯一分支节点
        List<SequenceFlow> result = new ArrayList<>();
        List<SequenceFlow> outgoingList = flowNode.getOutgoingFlows();
        if (outgoingList.isEmpty()) {
            throw new ActivitiException(String.format("Can't find next node %s", flowNode.getName()));
        }
        buildGoingList(outgoingList, result);
        if (result.size() > 1) {
            return flowNode;
        }
        for (SequenceFlow sequenceFlow : result) {
            FlowElement flowElement = bpmnModel.getMainProcess().getFlowElementMap().get(sequenceFlow.getTargetRef());
            if (flowElement instanceof InclusiveGateway) {
                return flowNode;
            }
        }
        //获取节点处理人信息
        for (SequenceFlow sequenceFlow : result) {
            TaskService taskService = commandContext.getProcessEngineConfiguration().getTaskService();
            FlowElement flowElement = bpmnModel.getMainProcess().getFlowElementMap().get(sequenceFlow.getTargetRef());
            if (flowElement instanceof EndEvent) {
                return flowNode;
            }
            if (flowElement instanceof InclusiveGateway) {
                return flowNode;
            }
            //任务、判断、固定人，只有这3个节点支持可跳跃
            if (!(flowElement instanceof KaiteUserTask) && !(flowElement instanceof KaiteFixedUserTask) && !(flowElement instanceof KaiteDecideUserTask)) {
                return flowNode;
            }
            KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowElement;
            //如果下个节点勾选了可跳跃
            if (!kaiteBaseUserTask.getTaskBasicConfig().getJump()) {
                return flowNode;
            }
            Boolean containsCurrentUser = taskService.assignerContainsCurrentUser(pdid, (KaiteBaseUserTask) flowElement, taskId, prod, bean);

            if (containsCurrentUser) {
                //如果包含 接着找下个节点
                return getNextElement(commandContext, (FlowNode) flowElement, false);
            }
        }
        return flowNode;
    }

    private void buildGoingList(List<SequenceFlow> outgoingList, List<SequenceFlow> result) {
        for (SequenceFlow sequenceFlow : outgoingList) {
            String conditionExpression = sequenceFlow.getConditionExpression();
            if (DelegateHelper.isBoolean(conditionExpression, bean)) {
                result.add(sequenceFlow);
            }
        }
    }
}
